home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / share / pyshared / launchpadbugs / attachmentsbase.py < prev    next >
Text File  |  2008-08-27  |  12KB  |  368 lines

  1. import os
  2.  
  3. import exceptions
  4. import utils
  5.  
  6. from lphelper import change_obj, LateBindingProperty
  7. from lpconstants import ATTACHMENTS
  8.  
  9.  
  10. class LPAttachment(object):
  11.     """ Returns an 'Attachment'-object
  12.     
  13.     * editable attributes:
  14.         .description: any text
  15.         .contenttype: any text
  16.         .is_patch: True/False
  17.         
  18.     * read-only attributes:
  19.         .id: hash(local_filename) for local files,
  20.             launchpadlibrarian-id for files uploaded to launchpadlibrarian.net
  21.         .is_down: True if a file is downloaded to ATTACHMENTS.ATTACHMENT_PATH
  22.         .is_up: True if file is uploaded to launchpadlibrarian.net
  23.         ...
  24.     TODO: work on docstring
  25.     """
  26.     def __init__(self, connection, url=None, localfilename=None, localfileobject=None,
  27.                     description=None, is_patch=None, contenttype=None, comment=None):
  28.         if [localfilename, localfileobject].count(None) == 0:
  29.             raise exceptions.PythonLaunchpadBugsValueError(msg="Attachment can not take localfilename and localfileobject")
  30.         self.__connection = connection
  31.         self.__description = description
  32.         self.__is_patch = is_patch
  33.         if self.__is_patch:
  34.             self.__contenttype = "text/plain"
  35.         else:
  36.             self.__contenttype = contenttype
  37.             
  38.         self.__local_filename = localfilename
  39.         self.__open_in_object = False
  40.         if localfileobject:
  41.             if isinstance(localfileobject, file):
  42.                 self.__local_fileobject = localfileobject
  43.             else:
  44.                 raise TypeError, "Attachment: localfileobject needs to be a file-like object"
  45.         else:
  46.             if self.__local_filename:
  47.                 self.__local_fileobject = open(self.__local_filename, "r")
  48.                 self.__open_in_object = True
  49.             else:
  50.                 self.__local_fileobject = None
  51.                 
  52.         self.__url = url
  53.         self.__text = None
  54.         self.__download_location = None
  55.         try:
  56.             self.__comment = int(comment)
  57.         except:
  58.             self.__comment = None
  59.         self.__cache = {"description": self.description,
  60.             "is_patch": self.is_patch, "contenttype": self.contenttype}
  61.  
  62.     def __repr__(self):
  63.         s = list()
  64.         if self.is_up:
  65.             s.append("(up: #%s)" %self.lp_id)
  66.         if self.is_down:
  67.             s.append("(down: %s)" %self.local_filename)
  68.         return "<Attachment %s>" %", ".join(s)
  69.         
  70.     def __del__(self):
  71.         if self.__open_in_object and self.__local_fileobject:
  72.             self.__local_fileobject.close()
  73.            
  74.     @property
  75.     def id(self):
  76.         if self.is_up:
  77.             return self.lp_id
  78.         else:
  79.             if self.__local_fileobject:
  80.                 return hash(self.__local_fileobject)
  81.             elif self.local_filename:
  82.                 return hash(self.local_filename)
  83.             raise ValueError, "unable to get hash of the given file"
  84.         
  85.     @property
  86.     def is_down(self):
  87.         """checks if
  88.             * file is already in attachment cache => change .__local_filename to cache-location => returns True
  89.             * file is in old cache location => move it to new location => change .__local_filename to cache-location => returns true
  90.             * file is in any other location => return True
  91.             * else return False
  92.         """
  93.         if self.__download_location:
  94.             return True
  95.         if self.is_up:
  96.             filename_old = \
  97.                 os.path.expanduser(os.path.join(ATTACHMENTS.ATTACHMENT_PATH,
  98.                     str(self.lp_id), self.lp_filename))
  99.             cache_filename = \
  100.                 os.path.expanduser(os.path.join(ATTACHMENTS.ATTACHMENT_PATH,
  101.                     self.sourcepackage, str(self.bugnumber),
  102.                     str(self.lp_id), self.lp_filename))
  103.             if os.path.exists(filename_old):
  104.                 os.renames(filename_old, cache_filename)
  105.             if os.path.exists(cache_filename):
  106.                 self.__local_filename = cache_filename
  107.                 return True
  108.         return bool(self.local_filename)
  109.         
  110.     @property
  111.     def is_up(self):
  112.         return bool(self.url)
  113.         
  114.     @property
  115.     def url(self):
  116.         return self.__url or ""
  117.     
  118.     @property
  119.     def lp_id(self):
  120.         if self.is_up:
  121.             return self.url.split("/")[-2]
  122.         
  123.     @property
  124.     def lp_filename(self):
  125.         if self.is_up:
  126.             return self.url.split("/")[-1]
  127.         else:
  128.             return ""
  129.             
  130.     def get_bugnumber(self):
  131.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  132.     bugnumber = LateBindingProperty(get_bugnumber)
  133.                     
  134.     def get_sourcepackage(self):
  135.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  136.     sourcepackage = LateBindingProperty(get_sourcepackage)
  137.                     
  138.             
  139.     def get_edit_url(self):
  140.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  141.     edit_url = LateBindingProperty(get_edit_url)
  142.             
  143.     @property
  144.     def local_filename(self):
  145.         return self.__local_filename
  146.         
  147.     @property
  148.     def local_fileobject(self):
  149.         return self.__local_fileobject
  150.             
  151.     def get_contenttype(self):
  152.         return self.__contenttype
  153.         
  154.     def set_contenttype(self, type):
  155.         if not self.is_patch:
  156.             self.__contenttype = type
  157.     contenttype = property(get_contenttype, set_contenttype, doc="contenttype of an attachment")
  158.     
  159.     def get_description(self):
  160.         return self.__description
  161.         
  162.     def set_description(self, text):
  163.         self.__description = str(text)
  164.     description = property(get_description, set_description, doc="description of an attachment")
  165.     
  166.     def get_ispatch(self):
  167.         return self.__is_patch
  168.         
  169.     def set_ispatch(self, value):
  170.         self.__is_patch = bool(value)
  171.     is_patch = property(get_ispatch, set_ispatch, "type of an attachment")
  172.         
  173.     def get_changed(self):
  174.         changed = set()
  175.         for k in self.__cache:
  176.             if self.__cache[k] != getattr(self, k):
  177.                 changed.add(k)
  178.         return frozenset(changed)
  179.     changed = property(get_changed, doc="get a list of changed attributes")
  180.     
  181.     
  182.     @property
  183.     def text(self):
  184.         """get content of an attachment"""
  185.         if not self.is_down:
  186.             self.download()
  187.         else:
  188.             self.read_local()
  189.         return self.__text
  190.        
  191.     def read_local(self):
  192.         if self.is_down and self.__text == None:
  193.             if not self.local_fileobject:
  194.                 self.__local_fileobject = open(self.local_filename, "r")
  195.             self.__text = self.local_fileobject.read()
  196.        
  197.     def download(self, location=None):
  198.         """ save attachment in ATTACHMENTS.ATTACHMENT_PATH if not already done """            
  199.         if location is None:
  200.             self.__local_filename = \
  201.                 os.path.expanduser(os.path.join(ATTACHMENTS.ATTACHMENT_PATH,
  202.                     self.sourcepackage, str(self.bugnumber), str(self.lp_id), self.lp_filename))
  203.         else:
  204.             path = os.path.expanduser(location)
  205.             self.__download_location = path
  206.             self.__local_filename = path
  207.                 
  208.         if self.is_up:
  209.             attachment = self.__connection.get(self.url)
  210.             self.__text = attachment.text
  211.             self.contenttype = attachment.contenttype
  212.             utils.lazy_makedir(os.path.dirname(self.local_filename))
  213.             attachment_file = open(self.local_filename, "w")
  214.             attachment_file.write(self.__text)
  215.             attachment_file.close()
  216.             
  217.     @property
  218.     def comment(self):
  219.         return self.__comment
  220.             
  221.             
  222. class LPAttachments(object):
  223.     def __init__(self, comments=None, attachments=None, parsed=False):
  224.         if comments is None:
  225.             self.parsed = True
  226.         else:
  227.             self.parsed = parsed
  228.             self.__comments = comments
  229.         self.__added = set()
  230.         self.__removed = set()
  231.         self._current = attachments or list()
  232.         self.__cache = self.__current[:]
  233.         
  234.     def _get_attachments(self):
  235.         r = list()
  236.         c = self.__comments
  237.         x = [i.component for i in c.changed]
  238.         for i in c:
  239.             if i in x:
  240.                 self.__added = self.__added.union(i.attachments)
  241.             else:
  242.                 r.extend(i.attachments)
  243.         r.extend(self.__added)
  244.         for i, a in enumerate(r[:]):
  245.             if a.id in self.__removed:
  246.                 del r[i]
  247.         return r        
  248.         
  249.     def _get_current(self):
  250.         if not self.parsed:
  251.             self.parse()
  252.         return self.__current
  253.         
  254.     def _set_current(self, list):
  255.         self.__current = list
  256.     _current = property(_get_current, _set_current)
  257.     
  258.         
  259.     def __getitem__(self, key):
  260.         if isinstance(key, LPAttachment):
  261.             key = key.id
  262.         list_id = [a.id for a in self._current]
  263.         if key in list_id:
  264.             return self._current[list_id.index(key)]
  265.         try:
  266.             return self._current[key]
  267.         except IndexError:
  268.             list_id = [a.id for a in self.__removed]
  269.             if key in list_id:
  270.                 raise ValueError, "This attachment has been removed"
  271.         raise IndexError, "could not find '%s' in attachments ('%s')" %(key, self._url or "unknown url")
  272.         
  273.     def __str__(self):
  274.         return str(self._current)
  275.         
  276.     def __repr__(self):
  277.         return "<Attachmentlist>"
  278.         
  279.     def __iter__(self):
  280.         for i in self._current:
  281.             yield i
  282.             
  283.     def __len__(self):
  284.         return len(self.__current)
  285.             
  286.     def filter(self, func):
  287.         for a in self._current:
  288.             if func(a):
  289.                 yield a
  290.         
  291.     def add(self, attachment):
  292.         if isinstance(attachment, LPAttachment):
  293.             self.__added.add(attachment)
  294.             self._current.append(attachment)
  295.         else:
  296.             #TODO: raise TypeError
  297.             raise IOError, "'attachment' must be an instance of 'Attachment'"
  298.         
  299.     def remove(self, key=None, func=None):
  300.         arg = [i for i in (key,func) if i is None]
  301.         assert len(arg) == 1, "exactly one argument needed"
  302.         if func:
  303.             key = list(self.filter(func))
  304.         def _isiterable(x):
  305.             try:
  306.                 i = iter(x)
  307.                 return not isinstance(x, str) or False
  308.             except TypeError:
  309.                 return False
  310.         if _isiterable(key):
  311.             for i in key:
  312.                 self.remove(key=i)
  313.             return True
  314.         try:
  315.             if hasattr(key, "id"):
  316.                 key = key.id
  317.             x = self[key]
  318.             self.__removed.add(x)
  319.             try:
  320.                 self.__current.remove(x)
  321.             except ValueError:
  322.                 raise RunTimeError, "this should not happen"
  323.         except (TypeError, IndexError):
  324.             raise TypeError, "unsupported type of 'key' in Attachment.remove()"
  325.         
  326.     def parse(self):
  327.         r = True
  328.         if self.parsed:
  329.             return True
  330.         if not self.__comments.parsed:
  331.             r = self.__comments.parse()
  332.         self.__cache = self._get_attachments()
  333.         self._current = self.__cache[:]
  334.         self.parsed = True
  335.         return r
  336.         
  337.     @property
  338.     def deleted(self):
  339.         return frozenset(self.__removed)
  340.     
  341.     @property
  342.     def added(self):
  343.         return frozenset(self.__added)
  344.     
  345.     @property
  346.     def changed(self):
  347.         changed = []
  348.         deleted = self.deleted
  349.         added = self.added
  350.         x = set()
  351.         for i in self.__comments.changed:
  352.             x = x.union(i.component.attachments)
  353.         for i in deleted:
  354.             changed.append(change_obj(i, action="deleted"))
  355.         for i in added:
  356.             if i in x:
  357.                 continue
  358.             changed.append(change_obj(i, action="added"))
  359.         for i in set(self.__cache) - added - deleted:
  360.             if i.changed:
  361.                 changed.append(change_obj(i))
  362.         return changed
  363.         
  364.     def commit(self, force_changes=False, ignore_lp_errors=True):
  365.         raise NotImplementedError, 'this method must be implemented by a concrete subclass'
  366.         
  367.         
  368.